package com.fbs.job.admin.core.thread;

import com.fbs.job.admin.core.config.FbsJobAdminConfig;
import com.fbs.job.admin.core.entity.FbsJobActuator;
import com.fbs.job.admin.core.entity.FbsJobRegistry;
import com.fbs.job.admin.mapper.FbsJobRegistryMapper;
import com.fbs.job.core.biz.model.RegistryParam;
import com.fbs.job.core.enums.RegistryConfig;
import com.fbs.job.core.model.ResultT;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.*;

public class JobRegistryHelper {
    private static Logger logger = LoggerFactory.getLogger(JobRegistryHelper.class);
    private static JobRegistryHelper instance = new JobRegistryHelper();
    public static JobRegistryHelper getInstance(){
        return instance;
    }

    private ThreadPoolExecutor registryOrRemoveThreadPool = null;
    private Thread registryMonitorThread;
    private volatile boolean toStop = false;
    private FbsJobRegistryMapper fbsJobRegistryMapper= FbsJobAdminConfig.getAdminConfig().getFbsJobRegistryMapper();

    public void start(){
        registryOrRemoveThreadPool = new ThreadPoolExecutor(2,
                10,
                30L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<Runnable>(2000),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "fbs-job, admin JobRegistryMonitorHelper-registryOrRemoveThreadPool-" + r.hashCode());
                    }
                },
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        r.run();
                        logger.warn(">>>>>>>>>>> fbs-job, registry or remove too fast, match threadpool rejected handler(run now).");

                    }
                }

        );

        registryMonitorThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!toStop){
                    try {
                        List<FbsJobActuator> actuatorList = FbsJobAdminConfig.getAdminConfig().
                                getFbsJobActuatorMapper().findByAddressType(0);

                        if (CollectionUtils.isNotEmpty(actuatorList)){
                            //移除长时间没注册的节点
                            fbsJobRegistryMapper.removeDeadRegister(RegistryConfig.DEAD_TIMEOUT, new Date());
                            // 刷新执行器的地址列表
                            HashMap<String, StringBuilder> appAddressMap = new HashMap<>(16);
                            List<FbsJobRegistry> list = fbsJobRegistryMapper.findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
                            if (CollectionUtils.isNotEmpty(list)) {
                                for (FbsJobRegistry item: list) {
                                    if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryActuator())) {
                                        String appName = item.getRegistryKey();
                                        StringBuilder registryBuilder = appAddressMap.get(appName);
                                        if (registryBuilder == null) {
                                            registryBuilder = new StringBuilder();
                                            if (!registryBuilder.toString().contains(item.getRegistryValue())) {
                                                registryBuilder.append(item.getRegistryValue());
                                            }
                                        }else{
                                            if (!registryBuilder.toString().contains(item.getRegistryValue())) {
                                                registryBuilder.append(",").append(item.getRegistryValue());
                                            }
                                        }
                                        appAddressMap.put(appName, registryBuilder);
                                    }
                                }
                            }
                            // fresh group address
                            for (FbsJobActuator group: actuatorList) {
                                StringBuilder registrySb = appAddressMap.get(group.getAppName());
                                group.setAddressList(registrySb==null?"":registrySb.toString());
                                group.setUpdateTime(new Date());
                                FbsJobAdminConfig.getAdminConfig().getFbsJobActuatorMapper().update(group);
                            }
                        }
                    }catch (Exception e){
                        if (!toStop) {
                            logger.error(">>>>>>>>>>> fbs-job, job registry monitor thread error:{}", e);
                        }
                    }

                    try {
                        TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
                    } catch (InterruptedException e) {
                        if (!toStop) {
                            logger.error(">>>>>>>>>>> fbs-job, job registry monitor thread error:{}", e);
                        }
                    }
                }
                logger.info(">>>>>>>>>>> fbs-job, job registry monitor thread stop");
            }
        });

        registryMonitorThread.setDaemon(true);
        registryMonitorThread.setName("fbs-job, admin JobRegistryMonitorHelper-registryMonitorThread");
        registryMonitorThread.start();

    }
    public void toStop(){
        toStop = true;

        // stop registryOrRemoveThreadPool
        registryOrRemoveThreadPool.shutdownNow();

        // stop monitir (interrupt and wait)
        registryMonitorThread.interrupt();
        try {
            registryMonitorThread.join();
        } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
        }
    }


    // ---------------------- helper ----------------------

    public ResultT<String> registry(RegistryParam registryParam) {

        // valid
        if (!StringUtils.hasText(registryParam.getRegistryActuator())
                || !StringUtils.hasText(registryParam.getRegistryKey())
                || !StringUtils.hasText(registryParam.getRegistryValue())) {
            return ResultT.error("Illegal Argument.") ;
        }

        // async execute
        registryOrRemoveThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                int ret = FbsJobAdminConfig.getAdminConfig().getFbsJobRegistryMapper()
                        .registryUpdate(registryParam.getRegistryActuator(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
                if (ret < 1) {
                    FbsJobAdminConfig.getAdminConfig().getFbsJobRegistryMapper()
                            .registrySave(registryParam.getRegistryActuator(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());

                    // fresh
                    freshActuatorRegistryInfo(registryParam);
                }
            }
        });

        return ResultT.success();
    }
    private void freshActuatorRegistryInfo(RegistryParam registryParam){
        // Under consideration, prevent affecting core tables
    }




    public ResultT<String> registryRemove(RegistryParam registryParam) {

        // valid
        if (!StringUtils.hasText(registryParam.getRegistryActuator())
                || !StringUtils.hasText(registryParam.getRegistryKey())
                || !StringUtils.hasText(registryParam.getRegistryValue())) {
            return new ResultT<String>(ResultT.FAIL_CODE, "Illegal Argument.");
        }

        // async execute
        registryOrRemoveThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                int ret = FbsJobAdminConfig.getAdminConfig().getFbsJobRegistryMapper().registryDelete(registryParam.getRegistryActuator(), registryParam.getRegistryKey(), registryParam.getRegistryValue());
                if (ret > 0) {
                    // fresh
                    freshActuatorRegistryInfo(registryParam);
                }
            }
        });

        return ResultT.success();
    }

}
